package jp.co.nes.awssupport.business.logic;

import java.time.YearMonth;
import java.time.ZonedDateTime;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.services.sqs.model.Message;
import com.amazonaws.services.stepfunctions.model.StartExecutionResult;

import jp.co.nes.awssupport.business.bean.CommonContainerBean.ProcessingResult;
import jp.co.nes.awssupport.business.bean.CompileContainerBean;
import jp.co.nes.awssupport.business.bean.CompileInfoBean;
import jp.co.nes.awssupport.business.logic.CompileProgressManage.CompletionState;
import jp.co.nes.awssupport.business.logic.CompileProgressManage.CountKey;
import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.utility.AwsStepFunctionsAsyncHelper;
import jp.co.nes.awssupport.common.utility.DateUtil;
import jp.co.nes.awssupport.common.utility.JsonUtil;

/**
 * 集計処理実行クラスです。<br>
 * 集計進捗状況の集計処理呼び出しに関する状態については、正常に完了状態になる or 失敗状態になる、
 * いずれかになる想定で実装されています。<br>
 * 失敗状態から、呼び出しに関する状態も含めてリランする場合(※1)は、失敗状態になっているものをクリアしてから
 * 実施する必要があります。<br>
 * 呼び出しに関する処理をスキップして、次処理から開始する場合(※2)は、クリアせずに処理を進めることが可能です。<br>
 * (※1)は、該当する連結アカウントの集計処理ステートマシン(StepFunctions)を呼び出すトピックを手動で作成して、
 * 処理管理ステートマシン(StepFunctions)からリランする場合になります。<br>
 * (※2)は、該当する連結アカウントの集計処理ステートマシン(StepFunctions)を手動で実行して、リランする場合になります。
 * また、集計前処理ステートマシン(StepFunctions)を実施し、その後、処理管理ステートマシン(StepFunctions)でリランする場合も、
 * 呼び出しに関する状態をクリアする必要はありません(集計前処理ステートマシン(StepFunctions)で状態は全てクリアされます)。
 */
public class CompileProcessExecutor implements ProcessExecutor {

    /** ロガー */
    private static Logger logger = LoggerFactory.getLogger(CompileProcessExecutor.class);

    /** AWSStepFunctionsAsyncヘルパー */
    private AwsStepFunctionsAsyncHelper helper;

    /** 処理対象となる全件数 */
    private int allCount;

    /** 呼び出した処理の実行中件数 */
    private int processingCount;

    /** 処理呼び出しが成功している件数 */
    private int successProcessStartCount;

    /** 呼び出し処理が失敗した件数 */
    private int failureProcessStartCount;

    /** 呼び出した処理の状態が不明な件数 */
    private int unknownCount;

    /**
     * デフォルトコンストラクタです。
     */
    public CompileProcessExecutor() {
        helper = new AwsStepFunctionsAsyncHelper();
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.business.logic.ProcessExecutor#confirm()
     */
    @Override
    public void confirm() {
        Map<CountKey, Integer> countMap = CompileProgressManage.getAllCount();
        int completionProcessStartAllCount = countMap.get(CountKey.COMPLETION_PROCESS_START_ALL_COUNT);
        int completionProcessAllCount = countMap.get(CountKey.COMPLETION_PROCESS_ALL_COUNT);
        int failureProcessAllCount = countMap.get(CountKey.FAILURE_PROCESS_ALL_COUNT);

        allCount = countMap.get(CountKey.ALL_COUNT);
        processingCount = completionProcessStartAllCount - completionProcessAllCount - failureProcessAllCount;
        successProcessStartCount = completionProcessStartAllCount;
        failureProcessStartCount = countMap.get(CountKey.FAILURE_PROCESS_START_ALL_COUNT);
        unknownCount = countMap.get(CountKey.UNKNOWN_ALL_COUNT);
        if (logger.isDebugEnabled()) {
            logger.debug("allCount=" + allCount);
            logger.debug("processingCount=" + allCount);
            logger.debug("successProcessStartCount=" + successProcessStartCount);
            logger.debug("failureProcessStartCount=" + failureProcessStartCount);
            logger.debug("unknownCount=" + unknownCount);
        }
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.business.logic.ProcessExecutor#getProcessingCount()
     */
    @Override
    public int getProcessingCount() {
        return processingCount;
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.business.logic.ProcessExecutor#getSuccessCount()
     */
    @Override
    public int getSuccessProcessStartCount() {
        return successProcessStartCount;
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.business.logic.ProcessExecutor#getFailureCount()
     */
    @Override
    public int getFailureProcessStartCount() {
        return failureProcessStartCount;
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.business.logic.ProcessExecutor#getUnknownCount()
     */
    @Override
    public int getUnknownCount() {
        return unknownCount;
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.business.logic.ProcessExecutor#getProcessingResult(int, int)
     */
    @Override
    public ProcessingResult getProcessingResult(int success, int failure) {
        int count = success + failure;

        if (allCount == 0) {
            return ProcessingResult.WAIT;
        }
        if (count == 0) {
            return ProcessingResult.WAIT;
        }
        if (allCount == count) {
            if (logger.isDebugEnabled()) {
                logger.debug("success=" + success);
                logger.debug("failureProcessStartCount=" + failure);
            }

            ProcessingResult processingResult = null;
            if (success == 0 && failure == 0) {
                // 通常発生しない。
                throw new CommonException("count is wrong(success=" + success + ",failure=" + failure + ")");
            } else if (success == 0 && failure > 0) {
                processingResult = ProcessingResult.FAILURE;
            } else if (success > 0 && failure == 0) {
                processingResult = ProcessingResult.SUCCESS;
            } else if (success > 0 && failure > 0) {
                processingResult = ProcessingResult.SOME_FAILURE;
            } else {
                // 通常発生しない。
                throw new CommonException("count is wrong(success=" + success + ",failure=" + failure + ")");
            }
            return processingResult;
        } else if (allCount > count) {
            return ProcessingResult.WAIT;
        } else {
            // 通常発生しない。
            throw new CommonException("allCount < count(allCount=" + allCount + ",count=" + count + ")");
        }
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.business.logic.ProcessExecutor#startProcessAsync(java.lang.String, java.lang.String, com.amazonaws.services.sqs.model.Message)
     */
    // processNameはstartProcessAsyncメソッドの中で使用していないが、将来の使用も考慮し、引数として残しておく。
    @Override
    public StartProcessResult startProcessAsync(String processName, String destination, Message message) {
        Map<String, String> messageBodyMap = JsonUtil.convertJsonStringToMap(message.getBody());
        String messageValue = messageBodyMap.get("Message");
        CompileInfoBean infoBean = JsonUtil.convertJsonStringToBean(messageValue, CompileInfoBean.class);
        CompileContainerBean<CompileInfoBean> containerBean = new CompileContainerBean<>();
        containerBean.setInfoBean(infoBean);

        String targetYearMonthValue = infoBean.getTargetYearMonthValue();
        String linkedAccountId = infoBean.getLinkedAccountId();
        ZonedDateTime now = DateUtil.getNowOfZonedDateTime();
        String nowValue = DateUtil.toString(now);
        String preName = nowValue + "-" + targetYearMonthValue + "-L" + linkedAccountId;

        if (logger.isDebugEnabled()) {
            logger.debug("containerBean=" + containerBean);
        }

        YearMonth targetYearMonth = DateUtil.toYearMonth(targetYearMonthValue);
        CompileProgressManage cpManage = new CompileProgressManage(targetYearMonth);

        if (!cpManage.isCompletion(linkedAccountId, CompletionState.TOPIC_PUBLISH)) {
            // トピック送信が完了状態になっていない場合、引き渡されたメッセージはゴミ、
            // または、トピックを送信したが、まだ完了状態の更新が完了していないもの。
            // よって、集計処理を呼び出さない。
            // この場合、ゴミメッセージは、メッセージ保持期間を過ぎるとキューから削除され、
            // トピックを送信したが、まだ完了状態の更新が完了していないものは、更新完了後、集計処理を呼び出す。
            // なお、何らかの異常が発生し、完了状態の更新が完了していなかったものは、
            // 集計処理を呼び出さない(この場合、正常に連結アカウントごとの呼び出したものは処理が進むが、
            // 処理管理、Sforce反映はタイムアウトになる)。
            logger.debug("TopicPublish not completion");
            return StartProcessResult.NO_NEED;
        }

        if (cpManage.isCompletion(linkedAccountId, CompletionState.PROCESS_START)) {
            // 既に処理されているので、何もせずに正常で処理を抜ける。
            logger.debug("ProcessStart completion");
            return StartProcessResult.NO_NEED_AND_MESSAGE_DELETE;
        }

        StartProcessResult startProcessResult = StartProcessResult.FAILURE;
        Future<StartExecutionResult> future = helper.startExecutionAsync(destination, preName, containerBean);
        try {
            StartExecutionResult startExecutionResult = future.get();
            startProcessResult = StartProcessResult.SUCCESS;

            if (logger.isDebugEnabled()) {
                StringBuilder builder = new StringBuilder();
                builder.append("StartExecutionResult=");
                builder.append(startExecutionResult.getStartDate());
                builder.append(": ");
                builder.append(startExecutionResult.getExecutionArn());
                logger.debug(builder.toString());
            }

            cpManage.completion(linkedAccountId, CompletionState.PROCESS_START);
        } catch (InterruptedException | ExecutionException e) {
            // 集計進捗状況を集計処理呼び出し失敗状態にし、ワーニングログを出力して処理を続行する。
            cpManage.failureProcessStart(linkedAccountId);
            logger.warn(e.getMessage(), e);
        }

        return startProcessResult;
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.business.logic.ProcessExecutor#release()
     */
    @Override
    public void release() {
        helper.shutdown();
        helper = null;
    }
}
